001 /* 002 * Copyright 2004-2005 Stephen McConnell 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.tools.tasks; 020 021 import java.io.File; 022 import java.io.IOException; 023 import java.io.InputStream; 024 import java.io.FileInputStream; 025 import java.util.Enumeration; 026 import java.util.Properties; 027 import java.util.StringTokenizer; 028 029 import net.dpml.library.Type; 030 import net.dpml.library.Resource; 031 import net.dpml.library.info.Scope; 032 033 import net.dpml.tools.Context; 034 035 import net.dpml.transit.Transit; 036 037 import org.apache.tools.ant.BuildException; 038 import org.apache.tools.ant.Project; 039 import org.apache.tools.ant.taskdefs.Exit; 040 import org.apache.tools.ant.taskdefs.optional.junit.BatchTest; 041 import org.apache.tools.ant.taskdefs.optional.junit.FormatterElement; 042 import org.apache.tools.ant.taskdefs.optional.junit.JUnitTask; 043 import org.apache.tools.ant.types.Environment; 044 import org.apache.tools.ant.types.FileSet; 045 import org.apache.tools.ant.types.Path; 046 import org.apache.tools.ant.types.Commandline; 047 048 /** 049 * JUnit test execution. 050 * 051 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 052 * @version 1.1.0 053 */ 054 public class JUnitTestTask extends GenericTask 055 { 056 /** 057 * Constant for lookup of mx value. 058 */ 059 public static final String MX_KEY = "project.test.mx"; 060 061 /** 062 * Constant test enabled key. 063 */ 064 public static final String TEST_ENABLED_KEY = "project.test.enabled"; 065 066 /** 067 * Constant test src directory key. 068 */ 069 public static final String TEST_SRC_KEY = "project.test.src"; 070 071 /** 072 * Constant test env directory key. 073 */ 074 public static final String TEST_ENV_KEY = "project.test.env"; 075 076 /** 077 * Constant test debug key. 078 */ 079 public static final String DEBUG_KEY = "project.test.debug"; 080 081 /** 082 * Constant test fork key. 083 */ 084 public static final String FORK_KEY = "project.test.fork"; 085 086 /** 087 * Constant test fork mode key. 088 */ 089 public static final String TEST_FORK_MODE_KEY = "project.test.fork.mode"; 090 091 /** 092 * Constant test halt-on-error key (default is false). 093 */ 094 public static final String HALT_ON_ERROR_KEY = "project.test.halt-on-error"; 095 096 /** 097 * Constant test halt-on-failure key (default is false). 098 */ 099 public static final String HALT_ON_FAILURE_KEY = "project.test.halt-on-failure"; 100 101 /** 102 * Constant test abort on error key - if true (the default) the build will fail 103 * if a test error occurs. 104 */ 105 public static final String ABORT_ON_ERROR_KEY = "project.test.exit-on-error"; 106 107 /** 108 * Constant test abort on failure key - if true (the default) the build will fail 109 * if a test failure occurs. 110 */ 111 public static final String ABORT_ON_FAILURE_KEY = "project.test.exit-on-failure"; 112 113 /** 114 * Constant cache path key. 115 */ 116 public static final String CACHE_PATH_KEY = "dpml.cache"; 117 118 /** 119 * Constant work dir key. 120 */ 121 public static final String WORK_DIR_KEY = "project.test.dir"; 122 123 /** 124 * the key for the include pattern for test cases 125 */ 126 public static final String TEST_INCLUDES_KEY = "project.test.includes"; 127 128 /** 129 * default value 130 */ 131 public static final String TEST_INCLUDES_VALUE = "**/*TestCase.java, **/*Test.java"; 132 133 /** 134 * the key for the exclude pattern for test cases 135 */ 136 public static final String TEST_EXCLUDES_KEY = "project.test.excludes"; 137 138 /** 139 * default value 140 */ 141 public static final String TEST_EXCLUDES_VALUE = "**/Abstract*.java, **/AllTest*.java"; 142 143 /** 144 * the key for the exclude pattern for test cases 145 */ 146 public static final String VERBOSE_KEY = "project.test.verbose"; 147 148 private static final String ERROR_KEY = "project.test.error"; 149 private static final String FAILURE_KEY = "project.test.failure"; 150 private static final String TEST_SRC_VALUE = "test"; 151 private static final String TEST_ENV_VALUE = "env"; 152 private static final boolean DEBUG_VALUE = true; 153 private static final boolean FORK_VALUE = true; 154 private static final boolean HALT_ON_ERROR_VALUE = false; 155 private static final boolean HALT_ON_FAILURE_VALUE = false; 156 157 private File m_source; 158 private String m_classPathRef; 159 private Path m_classPath; 160 161 /** 162 * Task initialization. 163 * @exception BuildException if a build error occurs. 164 */ 165 public void init() throws BuildException 166 { 167 if( !isInitialized() ) 168 { 169 super.init(); 170 final Project project = getProject(); 171 project.setNewProperty( DEBUG_KEY, "" + DEBUG_VALUE ); 172 project.setNewProperty( FORK_KEY, "" + FORK_VALUE ); 173 project.setNewProperty( TEST_SRC_KEY, "" + TEST_SRC_VALUE ); 174 project.setNewProperty( TEST_ENV_KEY, "" + TEST_ENV_VALUE ); 175 project.setNewProperty( HALT_ON_ERROR_KEY, "" + HALT_ON_ERROR_VALUE ); 176 project.setNewProperty( HALT_ON_FAILURE_KEY, "" + HALT_ON_FAILURE_VALUE ); 177 getContext().getPath( Scope.TEST ); 178 } 179 } 180 181 /** 182 * Set the id of the compilation classpath. 183 * @param id the classpath reference 184 */ 185 public void setClasspathRef( String id ) 186 { 187 m_classPathRef = id; 188 } 189 190 /** 191 * Set the classpath. 192 * @param path the classpath 193 */ 194 public void setClasspath( Path path ) 195 { 196 m_classPath = path; 197 } 198 199 /** 200 * Set the directory containing the unit test source files. 201 * @param source the test source directory 202 */ 203 public void setSrc( File source ) 204 { 205 m_source = source; 206 } 207 208 private Path getClasspath() 209 { 210 if( null != m_classPath ) 211 { 212 return m_classPath; 213 } 214 else if( null != m_classPathRef ) 215 { 216 return (Path) getProject().getReference( m_classPathRef ); 217 } 218 else 219 { 220 final String error = 221 "Missing classpathref or classpath argument."; 222 throw new BuildException( error, getLocation() ); 223 } 224 } 225 226 /** 227 * Task execution. 228 * @exception BuildException if a build error occurs. 229 */ 230 public void execute() throws BuildException 231 { 232 if( !isTestingEnabled() ) 233 { 234 return; 235 } 236 237 final Context context = getContext(); 238 final Project project = getProject(); 239 final File src = context.getTargetBuildTestDirectory(); 240 if( src.exists() ) 241 { 242 final File working = context.getTargetTestDirectory(); 243 final Path classpath = getClasspath(); 244 245 executeUnitTests( src, classpath, working ); 246 247 if( getBooleanProperty( ABORT_ON_ERROR_KEY, true ) ) 248 { 249 final String error = project.getProperty( ERROR_KEY ); 250 if( null != error ) 251 { 252 final String message = 253 "One or more unit test errors occured."; 254 fail( message ); 255 } 256 } 257 258 if( getBooleanProperty( ABORT_ON_FAILURE_KEY, true ) ) 259 { 260 final String failure = project.getProperty( FAILURE_KEY ); 261 if( null != failure ) 262 { 263 final String message = 264 "One or more unit test failures occured."; 265 fail( message ); 266 } 267 } 268 } 269 } 270 271 private void executeUnitTests( final File src, final Path classpath, File working ) 272 { 273 final Project project = getProject(); 274 log( "Test classpath: " + classpath, Project.MSG_VERBOSE ); 275 final FileSet fileset = createFileSet( src ); 276 final JUnitTask junit = (JUnitTask) project.createTask( "junit" ); 277 junit.setTaskName( getTaskName() ); 278 279 final JUnitTask.SummaryAttribute summary = getSummaryAttribute(); 280 junit.setPrintsummary( summary ); 281 282 junit.setShowOutput( true ); 283 junit.setTempdir( working ); 284 junit.setReloading( true ); 285 junit.setFiltertrace( true ); 286 287 junit.createClasspath().add( classpath ); 288 289 Context context = getContext(); 290 String verbose = getVerboseArgument(); 291 if( null != verbose ) 292 { 293 Commandline.Argument arg = junit.createJvmarg(); 294 arg.setValue( "-verbose:" + verbose ); 295 } 296 297 final File reports = getContext().getTargetReportsTestDirectory(); 298 mkDir( reports ); 299 300 final BatchTest batch = junit.createBatchTest(); 301 batch.addFileSet( fileset ); 302 batch.setTodir( reports ); 303 304 final FormatterElement plain = newConfiguredFormatter( "plain" ); 305 junit.addFormatter( plain ); 306 307 final FormatterElement xml = newConfiguredFormatter( "xml" ); 308 junit.addFormatter( xml ); 309 310 final Environment.Variable work = new Environment.Variable(); 311 work.setKey( WORK_DIR_KEY ); 312 work.setValue( working.toString() ); 313 junit.addConfiguredSysproperty( work ); 314 315 final Environment.Variable testBaseDir = new Environment.Variable(); 316 testBaseDir.setKey( "project.test.dir" ); 317 testBaseDir.setValue( working.toString() ); 318 junit.addConfiguredSysproperty( testBaseDir ); 319 320 final Environment.Variable targetDir = new Environment.Variable(); 321 targetDir.setKey( "project.target.dir" ); 322 targetDir.setValue( getContext().getTargetDirectory().toString() ); 323 junit.addConfiguredSysproperty( targetDir ); 324 325 final Environment.Variable deliverablesDir = new Environment.Variable(); 326 deliverablesDir.setKey( "project.target.deliverables.dir" ); 327 deliverablesDir.setValue( getContext().getTargetDeliverablesDirectory().toString() ); 328 junit.addConfiguredSysproperty( deliverablesDir ); 329 330 final Environment.Variable basedir = new Environment.Variable(); 331 basedir.setKey( "basedir" ); 332 basedir.setValue( project.getBaseDir().toString() ); 333 junit.addConfiguredSysproperty( basedir ); 334 335 final Environment.Variable basedir2 = new Environment.Variable(); 336 basedir2.setKey( "project.basedir" ); 337 basedir2.setValue( project.getBaseDir().toString() ); 338 junit.addConfiguredSysproperty( basedir2 ); 339 340 final Environment.Variable cache = new Environment.Variable(); 341 cache.setKey( CACHE_PATH_KEY ); 342 cache.setValue( getCachePath() ); 343 junit.addConfiguredSysproperty( cache ); 344 345 final File policy = new File( working, "security.policy" ); 346 if( policy.exists() ) 347 { 348 final Environment.Variable security = new Environment.Variable(); 349 security.setKey( "java.security.policy" ); 350 security.setValue( policy.toString() ); 351 junit.addConfiguredSysproperty( security ); 352 } 353 354 setupTestProperties( junit, project ); 355 356 final File logProperties = new File( working, "logging.properties" ); 357 if( logProperties.exists() ) 358 { 359 final Environment.Variable log = new Environment.Variable(); 360 log.setKey( "dpml.logging.config" ); 361 try 362 { 363 log.setValue( logProperties.toURL().toString() ); 364 } 365 catch( IOException e ) 366 { 367 final String error = 368 "Unexpected file to url error." 369 + "\nFile: " + logProperties; 370 throw new BuildException( error, e ); 371 } 372 junit.addConfiguredSysproperty( log ); 373 } 374 375 String formatter = getResource().getProperty( "java.util.logging.config.class" ); 376 if( null != formatter ) 377 { 378 final Environment.Variable logging = new Environment.Variable(); 379 logging.setKey( "java.util.logging.config.class" ); 380 if( "dpml".equals( formatter ) ) 381 { 382 logging.setValue( "net.dpml.util.ConfigurationHandler" ); 383 } 384 else 385 { 386 logging.setValue( formatter ); 387 } 388 junit.addConfiguredSysproperty( logging ); 389 } 390 391 final Environment.Variable endorsed = new Environment.Variable(); 392 endorsed.setKey( "java.endorsed.dirs" ); 393 endorsed.setValue( new File( Transit.DPML_SYSTEM, "lib/endorsed" ).getAbsolutePath() ); 394 junit.addConfiguredSysproperty( endorsed ); 395 396 configureDeliverableSysProperties( junit ); 397 configureForExecution( junit ); 398 399 junit.setErrorProperty( ERROR_KEY ); 400 junit.setFailureProperty( FAILURE_KEY ); 401 final boolean haltOnErrorPolicy = getHaltOnErrorPolicy(); 402 if( haltOnErrorPolicy ) 403 { 404 junit.setHaltonerror( true ); 405 } 406 final boolean haltOnFailurePolicy = getHaltOnFailurePolicy(); 407 if( haltOnFailurePolicy ) 408 { 409 junit.setHaltonfailure( true ); 410 } 411 412 junit.init(); 413 junit.execute(); 414 } 415 416 private void setupTestProperties( JUnitTask junit, Project project ) 417 { 418 File base = project.getBaseDir(); 419 final File properties = new File( base, "test.properties" ); 420 if( properties.exists() ) 421 { 422 Properties props = new Properties(); 423 try 424 { 425 InputStream input = new FileInputStream( properties ); 426 props.load( input ); 427 Enumeration enum = props.propertyNames(); 428 while( enum.hasMoreElements() ) 429 { 430 String name = (String) enum.nextElement(); 431 final Environment.Variable v = new Environment.Variable(); 432 v.setKey( name ); 433 v.setValue( props.getProperty( name ) ); 434 junit.addConfiguredSysproperty( v ); 435 } 436 } 437 catch( IOException ioe ) 438 { 439 final String error = 440 "Unexpected IO error while reading " + properties.toString(); 441 throw new BuildException( error, ioe, getLocation() ); 442 } 443 } 444 } 445 446 private void configureForExecution( JUnitTask task ) 447 { 448 if( getForkProperty() ) 449 { 450 task.setFork( true ); 451 Project project = getProject(); 452 task.setDir( project.getBaseDir() ); 453 JUnitTask.ForkMode mode = getForkMode(); 454 if( null == mode ) 455 { 456 log( "Executing forked test." ); 457 } 458 else 459 { 460 log( "Executing forked test with mode: '" + mode + "'." ); 461 task.setForkMode( mode ); 462 } 463 String mx = getContext().getProperty( MX_KEY ); 464 if( null != mx ) 465 { 466 task.setMaxmemory( mx ); 467 } 468 } 469 else 470 { 471 log( "executing in local jvm" ); 472 JUnitTask.ForkMode mode = new JUnitTask.ForkMode( "once" ); 473 task.setForkMode( mode ); 474 task.setFork( false ); 475 } 476 } 477 478 private void configureDeliverableSysProperties( JUnitTask task ) 479 { 480 try 481 { 482 Context context = getContext(); 483 Resource resource = context.getResource(); 484 Type[] types = context.getResource().getTypes(); 485 for( int i=0; i<types.length; i++ ) 486 { 487 Type type = types[i]; 488 String id = type.getID(); 489 File file = context.getTargetDeliverable( id ); 490 String path = file.getCanonicalPath(); 491 final Environment.Variable variable = new Environment.Variable(); 492 variable.setKey( "project.deliverable." + id + ".path" ); 493 variable.setValue( path ); 494 task.addConfiguredSysproperty( variable ); 495 } 496 } 497 catch( IOException ioe ) 498 { 499 final String error = 500 "Unexpected IO error while building deliverable filename properties."; 501 throw new BuildException( error, ioe, getLocation() ); 502 } 503 } 504 505 private FileSet createFileSet( File src ) 506 { 507 final FileSet fileset = new FileSet(); 508 fileset.setDir( src ); 509 addIncludes( fileset ); 510 addExcludes( fileset ); 511 return fileset; 512 } 513 514 private void addIncludes( FileSet set ) 515 { 516 String pattern = getTestIncludes(); 517 log( "Test includes=" + pattern, Project.MSG_VERBOSE ); 518 StringTokenizer tokenizer = new StringTokenizer( pattern, ", ", false ); 519 while( tokenizer.hasMoreTokens() ) 520 { 521 String item = tokenizer.nextToken(); 522 set.createInclude().setName( item ); 523 } 524 } 525 526 private void addExcludes( FileSet set ) 527 { 528 String pattern = getTestExcludes(); 529 log( "Test excludes=" + pattern, Project.MSG_VERBOSE ); 530 StringTokenizer tokenizer = new StringTokenizer( pattern, ", ", false ); 531 while( tokenizer.hasMoreTokens() ) 532 { 533 String item = tokenizer.nextToken(); 534 set.createExclude().setName( item ); 535 } 536 } 537 538 private String getTestIncludes() 539 { 540 String includes = getContext().getProperty( TEST_INCLUDES_KEY ); 541 if( null != includes ) 542 { 543 return includes; 544 } 545 else 546 { 547 return TEST_INCLUDES_VALUE; 548 } 549 } 550 551 private String getTestExcludes() 552 { 553 String excludes = getContext().getProperty( TEST_EXCLUDES_KEY ); 554 if( null != excludes ) 555 { 556 return excludes; 557 } 558 else 559 { 560 return TEST_EXCLUDES_VALUE; 561 } 562 } 563 564 private String getCachePath() 565 { 566 final String value = getContext().getProperty( CACHE_PATH_KEY ); 567 if( null != value ) 568 { 569 return value; 570 } 571 else 572 { 573 File cache = (File) getProject().getReference( "dpml.cache" ); 574 return cache.toString(); 575 } 576 } 577 578 private boolean getDebugProperty() 579 { 580 return getBooleanProperty( DEBUG_KEY, DEBUG_VALUE ); 581 } 582 583 private boolean getForkProperty() 584 { 585 return getBooleanProperty( FORK_KEY, FORK_VALUE ); 586 } 587 588 private JUnitTask.ForkMode getForkMode() 589 { 590 final String value = getContext().getProperty( TEST_FORK_MODE_KEY ); 591 if( null == value ) 592 { 593 return null; 594 } 595 else 596 { 597 return new JUnitTask.ForkMode( value ); 598 } 599 } 600 601 private boolean getBooleanProperty( final String key, final boolean fallback ) 602 { 603 final String value = getContext().getProperty( key ); 604 if( null == value ) 605 { 606 return fallback; 607 } 608 else 609 { 610 return Project.toBoolean( value ); 611 } 612 } 613 614 private void fail( final String message ) 615 { 616 final Exit exit = (Exit) getProject().createTask( "fail" ); 617 exit.setMessage( message ); 618 exit.init(); 619 exit.execute(); 620 } 621 622 private boolean isTestingEnabled() 623 { 624 final String enabled = getContext().getProperty( TEST_ENABLED_KEY, "true" ); 625 return "true".equals( enabled ); 626 } 627 628 private JUnitTask.SummaryAttribute getSummaryAttribute() 629 { 630 final Project project = getProject(); 631 final JUnitTask.SummaryAttribute summary = new JUnitTask.SummaryAttribute(); 632 summary.setValue( "on" ); 633 return summary; 634 } 635 636 private boolean getHaltOnErrorPolicy() 637 { 638 return getBooleanProperty( 639 HALT_ON_ERROR_KEY, HALT_ON_ERROR_VALUE ); 640 } 641 642 private boolean getHaltOnFailurePolicy() 643 { 644 return getBooleanProperty( 645 HALT_ON_FAILURE_KEY, HALT_ON_FAILURE_VALUE ); 646 } 647 648 private String getVerboseArgument() 649 { 650 Context context = getContext(); 651 return context.getProperty( "project.test.verbose" ); 652 } 653 654 private FormatterElement newConfiguredFormatter( String type ) 655 { 656 final FormatterElement formatter = new FormatterElement(); 657 final FormatterElement.TypeAttribute attribute = new FormatterElement.TypeAttribute(); 658 attribute.setValue( type ); 659 formatter.setType( attribute ); 660 return formatter; 661 } 662 663 }